package es.ubu.ceudaaj.exercise.ud02.t04.l03;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import es.ubu.ceudaaj.exercise.ud02.t04.l03.model.Contactos;

/**
 * Class representing a list of contacts that can be read and written into and
 * from an xml file.
 * 
 * This version uses DOM to process the xml.
 * 
 */
public class AgendaDOM implements Agenda {

	private static final String XML_INPUT_PATH = "src/main/resources/xml/Contactos.xml";
	private static final String XML_OUTPUT_PATH = "src/main/resources/xml/Contactos_new.xml";

	// Attribute to keep a reference to the current XML in memory.
	private Document doc;

	private static AgendaDOM instance = null;

	/**
	 * Private constructor This class should only be used as singleton
	 */
	private AgendaDOM() {
	}

	/**
	 * Returns a DOM based agenda
	 * 
	 * @return AgendaDOM
	 */
	public static AgendaDOM getInstance() {
		if (instance == null)
			instance = new AgendaDOM();
		return instance;
	}

	/**
	 * Main method responsible for printing the console menu and managing the
	 * user selections.
	 * 
	 * @param args
	 * @throws IOException
	 */
	// public static void main( String[] args ) throws IOException {
	// int selectedOption = 0;
	// int selectedContactId = 0;
	// String search = null;
	//
	// // An instance of this class that will represent the current list of
	// contacts.
	// AgendaDOM agenda = new AgendaDOM();
	//
	// // Menu loop.
	// while (true) {
	// selectedContactId = 0;
	//
	// Menu.printMenuHeader();
	//
	// agenda.listContacts();
	//
	// Menu.printMainMenu();
	//
	// selectedOption = Menu.readOptionFromConsole();
	//
	// switch (selectedOption) {
	// case 1:
	// agenda.loadXML();
	// Menu.pause();
	// break;
	// case 2:
	// agenda.saveXML();
	// Menu.pause();
	// break;
	// case 3:
	// agenda.createNewContact();
	// break;
	// case 4:
	// selectedContactId = 0;
	// System.out.print("Seleccione el nº de Contacto: ");
	// if ((selectedContactId = Menu.readOptionFromConsole()) > 0) {
	// agenda.printContactDetail(selectedContactId);
	// } else {
	// System.out.println("No es un valor valido");
	// }
	// Menu.pause();
	// break;
	// case 5:
	// selectedContactId = 0;
	// System.out.print("Seleccione el nº de Contacto:");
	// if ((selectedContactId = Menu.readOptionFromConsole()) > 0) {
	// agenda.editContact(selectedContactId);
	// } else {
	// System.out.println("No es un valor valido");
	// }
	// Menu.pause();
	// break;
	// case 6:
	// selectedContactId = 0;
	// System.out.print("Seleccione el nº de Contacto: ");
	// if ((selectedContactId = Menu.readOptionFromConsole()) > 0) {
	// agenda.deleteContact(selectedContactId);
	// } else {
	// System.out.println("No es un valor valido");
	// }
	// Menu.pause();
	// break;
	// case 7:
	// System.out.print("Nombre del contacto buscado: ");
	// search = Menu.readStringFromConsole();
	// if ( search != null && !search.isEmpty()) {
	// agenda.searchByName(search);
	// } else {
	// System.out.println("No es un valor valido");
	// }
	// Menu.pause();
	// break;
	// case 8:
	// System.out.print("Cargo del contacto buscado: ");
	// search = Menu.readStringFromConsole();
	// if ( search != null && !search.isEmpty()) {
	// agenda.searchByJob(search);
	// } else {
	// System.out.println("No es un valor valido");
	// }
	// Menu.pause();
	// break;
	// case 9:
	// System.out.print("C. Postal del contacto buscado: ");
	// search = Menu.readStringFromConsole();
	// if ( search != null && !search.isEmpty()) {
	// agenda.searchByPostalCode(search);
	// } else {
	// System.out.println("No es un valor valido");
	// }
	// Menu.pause();
	// break;
	// default:
	// System.out.println("No es una opcion valida.");
	// Menu.pause();
	// break;
	// }
	// }
	// }

	/**
	 * Shows a list of current contacts on console. (Only name and surnames).
	 */
	/*
	 * public void listContacts() { NodeList contacts; if (doc != null &&
	 * (contacts = doc.getElementsByTagName("Contactos")) != null &&
	 * contacts.getLength() > 0){ contacts =
	 * doc.getElementsByTagName("Contactos"); for (int i=0; i<
	 * contacts.getLength(); i++) { Element contact = (Element)contacts.item(i);
	 * System.out.println((i + 1) + ". " +
	 * contact.getElementsByTagName("Apellidos").item(0).getTextContent() + ", "
	 * + contact.getElementsByTagName("Nombre").item(0).getTextContent()); } }
	 * else { System.out.println("(lista vacia)"); } }
	 */

	/**
	 * Prints detailed information of selected user on console.
	 * 
	 * @param index
	 *            Position of selected contact.
	 * @throws IOException
	 */
	/*
	 * public void printContactDetail(int index) throws IOException { NodeList
	 * contacts; StringBuilder sb; if (doc != null && ((contacts =
	 * doc.getElementsByTagName("Contactos")) != null) && contacts.getLength()
	 * >= index && index > 0) { Element cont = (Element) contacts.item(index -
	 * 1); sb = new StringBuilder("Detalle del contacto " + index + ": "); if
	 * (cont.getElementsByTagName("IdContacto") != null) {
	 * sb.append("\n\tID: ").
	 * append(cont.getElementsByTagName("IdContacto").item(0).getTextContent());
	 * } sb.append("\n\tNombre: "); if
	 * (cont.getElementsByTagName("Estimado").item(0) != null) {
	 * sb.append(cont.getElementsByTagName
	 * ("Estimado").item(0).getTextContent()); } if
	 * (cont.getElementsByTagName("Nombre").item(0) != null) {
	 * sb.append(cont.getElementsByTagName
	 * ("Nombre").item(0).getTextContent()).append(" "); } if
	 * (cont.getElementsByTagName("Apellidos").item(0) != null) {
	 * sb.append(cont.
	 * getElementsByTagName("Apellidos").item(0).getTextContent()); } if
	 * (cont.getElementsByTagName("Direccion").item(0) != null) {
	 * sb.append("\n\tDireccion: "
	 * ).append(cont.getElementsByTagName("Direccion")
	 * .item(0).getTextContent()); } if
	 * (cont.getElementsByTagName("CodPostal").item(0) != null) {
	 * sb.append(", ")
	 * .append(cont.getElementsByTagName("CodPostal").item(0).getTextContent());
	 * } if (cont.getElementsByTagName("Ciudad").item(0) != null) {
	 * sb.append(", "
	 * ).append(cont.getElementsByTagName("Ciudad").item(0).getTextContent()); }
	 * if (cont.getElementsByTagName("EdoOProv").item(0) != null) {
	 * sb.append(", "
	 * ).append(cont.getElementsByTagName("EdoOProv").item(0).getTextContent());
	 * } if (cont.getElementsByTagName("Pais").item(0) != null) {
	 * sb.append(", ")
	 * .append(cont.getElementsByTagName("Pais").item(0).getTextContent()); } if
	 * (cont.getElementsByTagName("Cargo").item(0) != null) {
	 * sb.append("\n\tCargo: "
	 * ).append(cont.getElementsByTagName("Cargo").item(0).getTextContent()); }
	 * if (cont.getElementsByTagName("TelefonoTrabajo").item(0) != null) {
	 * sb.append
	 * ("\n\tTfno. trabajo: ").append(cont.getElementsByTagName("TelefonoTrabajo"
	 * ).item(0).getTextContent()); } if
	 * (cont.getElementsByTagName("ExtensionTrabajo").item(0) != null) {
	 * sb.append
	 * ("\n\tExt. trabajo: ").append(cont.getElementsByTagName("ExtensionTrabajo"
	 * ).item(0).getTextContent()); } if
	 * (cont.getElementsByTagName("NumFax").item(0) != null) {
	 * sb.append("\n\tFax: "
	 * ).append(cont.getElementsByTagName("NumFax").item(0).getTextContent()); }
	 * if (cont.getElementsByTagName("NomCorreoElectronico").item(0) != null) {
	 * sb.append("\n\tEmail: ").append(cont.getElementsByTagName(
	 * "NomCorreoElectronico").item(0).getTextContent()); } //
	 * //System.out.println("Tipo contacto: "); //TODO //
	 * //System.out.println("Llamadas: ");//TODO System.out.println(sb); } else
	 * { System.out.println("No existe en la lista de contactos."); } }
	 */

	/**
	 * Creates a new contact and adds it to the XML representation in memory.
	 * 
	 * @throws IOException
	 */
	public void createNewContact(Contactos nuevoContacto) {
		InputStreamReader isr = new InputStreamReader(System.in);
		BufferedReader br = new BufferedReader(isr);
		String name = null;
		String surnames = null;
		String phone = null;
		String mail = null;
		try {
			System.out.print("Introduzca Nombre: ");
			name = br.readLine();
			System.out.print("Introduzca Apellidos: ");
			surnames = br.readLine();
			System.out.print("Introduzca Telefono: ");
			phone = br.readLine();
			System.out.print("Introduzca eMail: ");
			mail = br.readLine();
		} catch (IOException e1) {
			e1.printStackTrace();
		}

		if (doc == null) {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			try {
				doc = factory.newDocumentBuilder().newDocument();
				Element dataroot = doc.createElement("dataroot");
				SimpleDateFormat sdf = new SimpleDateFormat(
						"yyyy-MM-dd'T'HH:mm:ss");
				String date = sdf.format(new Date());
				dataroot.setAttribute("generated", date);
				doc.appendChild(dataroot);
			} catch (ParserConfigurationException e) {
				System.err.println("Error al crear el documento XML.");
			}
		}

		Node dataroot = doc.getElementsByTagName("dataroot").item(0);

		Element contact = doc.createElement("Contactos");

		Element contactId = doc.createElement("IdContacto");
		contactId.appendChild(doc.createTextNode(String.valueOf(doc
				.getElementsByTagName("Contactos").getLength() + 1)));
		contact.appendChild(contactId);

		Element contactSurnames = doc.createElement("Apellidos");
		contactSurnames.appendChild(doc.createTextNode(surnames));
		contact.appendChild(contactSurnames);

		Element contactName = doc.createElement("Nombre");
		contactName.appendChild(doc.createTextNode(name));
		contact.appendChild(contactName);

		Element contactPhone = doc.createElement("TelefonoTrabajo");
		contactName.appendChild(doc.createTextNode(phone));
		contact.appendChild(contactPhone);

		Element contactMail = doc.createElement("nomCorreoElectronico");
		contactName.appendChild(doc.createTextNode(mail));
		contact.appendChild(contactMail);

		dataroot.appendChild(contact);
	}

	public Contactos seeContact(int index) {

		Contactos nuevoContacto = null;

		return nuevoContacto;

	}

	/**
	 * Method for editing the information for the selected contact in the XML
	 * representation in memory.
	 * 
	 * @param index
	 *            Position of selected contact.
	 * @throws IOException
	 */
	public void editContact(Contactos nuevoContacto, int index) {
		NodeList contacts;

		if (doc != null
				&& ((contacts = doc.getElementsByTagName("Contactos")) != null)
				&& contacts.getLength() >= index && index > 0) {

			Element cont = (Element) contacts.item(index - 1);
			String oldName = cont.getElementsByTagName("Nombre").item(0)
					.getTextContent();
			String oldSurnames = cont.getElementsByTagName("Apellidos").item(0)
					.getTextContent();
			String oldPhone = cont.getElementsByTagName("TelefonoTrabajo")
					.item(0).getTextContent();
			String oldMail = cont.getElementsByTagName("nomCorreoElectronico")
					.item(0).getTextContent();

			InputStreamReader isr = new InputStreamReader(System.in);
			BufferedReader br = new BufferedReader(isr);

			String name = null;
			String surnames = null;
			String phone = null;
			String mail = null;

			try {

				System.out
						.print("Introduzca Nombre (o pulse 'Enter' para mantener '"
								+ oldName + "'): ");
				name = br.readLine();

				if (name.isEmpty()) {
					name = oldName;
				}
				System.out
						.print("Introduzca Apellidos (pulse 'Enter' para mantener '"
								+ oldSurnames + "'): ");
				surnames = br.readLine();
				if (surnames.isEmpty()) {
					surnames = oldSurnames;
				}
				System.out
						.print("Introduzca TelefonoTrabajo (o pulse 'Enter' para mantener '"
								+ oldPhone + "'): ");
				phone = br.readLine();
				if (phone.isEmpty()) {
					phone = oldName;
				}
				System.out
						.print("Introduzca eMail (o pulse 'Enter' para mantener '"
								+ oldMail + "'): ");
				mail = br.readLine();
				if (mail.isEmpty()) {
					mail = oldName;
				}
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				cont.getElementsByTagName("Nombre").item(0)
						.setTextContent(name);
				cont.getElementsByTagName("Apellidos").item(0)
						.setTextContent(surnames);
				cont.getElementsByTagName("TelefonoTrabajo").item(0)
						.setTextContent(phone);
				cont.getElementsByTagName("nomCorreoElectronico").item(0)
						.setTextContent(mail);

				System.out.println("Se ha editado correctamente el contacto");
			}
		} else {
			System.out.println("No existe en la lista de contactos.");
		}
	}

	/**
	 * Removes the selected contact from the XML representation in memory.
	 * 
	 * @param index
	 *            Position of selected contact.
	 */
	public void deleteContact(int index) {
		NodeList contacts;
		if (doc != null
				&& ((contacts = doc.getElementsByTagName("Contactos")) != null)
				&& contacts.getLength() >= index && index > 0) {
			Element cont = (Element) contacts.item(index - 1);
			cont.getParentNode().removeChild(cont);
			System.out.println("Contacto borrado.");
		} else {
			System.out.println("No existe en la lista de contactos.");
		}
	}

	/**
	 * Writes the current XML in a file.
	 */
	public void saveXML() {
		if (doc != null) {
			TransformerFactory transformerFactory = TransformerFactory
					.newInstance();
			Transformer transformer;
			try {
				transformer = transformerFactory.newTransformer();
				DOMSource source = new DOMSource(doc);
				StreamResult result = new StreamResult(
						new File(XML_OUTPUT_PATH));
				transformer.transform(source, result);
				System.out.println("Se ha guardado correctamente el fichero "
						+ XML_OUTPUT_PATH);
			} catch (TransformerConfigurationException e) {
				System.err.println("Error guardando el fichero XML: " + e);
			} catch (TransformerException e) {
				System.err.println("Error guardando el fichero XML." + e);
			}
		} else {
			System.out.println("Aun no existe ningun contacto");
		}
	}

	/**
	 * Reads the input XML file.
	 */
	public void loadXML() {
		// Create a DOM builder and parse the fragment
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		try {
			doc = factory.newDocumentBuilder().parse(new File(XML_INPUT_PATH));
			System.out.println("Se ha leido el fichero " + XML_INPUT_PATH);
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Search contacts with the given name in the XML in memory.
	 * 
	 * Uses XPath for the search.
	 * 
	 * @param name
	 *            Name of the contact to search.
	 */
	public List<String> searchByName(String name) {
		String predicate = "Nombre='" + name + "'";
		xpathSearch(predicate);
		List<String> result = null;
		return result;
	}

	/**
	 * Search contacts with the given job in the XML in memory.
	 * 
	 * Uses XPath for the search.
	 * 
	 * @param name
	 *            Job of the contact to search.
	 */
	public List<String> searchByJob(String job) {
		String predicate = "Cargo='" + job + "'";
		xpathSearch(predicate);
		List<String> result = null;
		return result;
	}

	/**
	 * Search contacts with the given postal code in the XML in memory.
	 * 
	 * Uses XPath for the search.
	 * 
	 * @param name
	 *            Postal code of the contact to search.
	 */
	public List<String> searchByPostalCode(String postalCode) {
		String predicate = "CodPostal='" + postalCode + "'";
		xpathSearch(predicate);
		List<String> result = null;
		return result;
	}

	/**
	 * Prepares a query to evaluate with XPath and executes the query.
	 * 
	 * @param predicate
	 *            The predicate specifying the specific search to do.
	 */
	public void xpathSearch(String predicate) {
		if (doc != null) {
			List<String> resultNames = evaluateXPathQuery("//Contactos["
					+ predicate + "]/Nombre/text()");
			List<String> resultSurnames = evaluateXPathQuery("//Contactos["
					+ predicate + "]/Apellidos/text()");

			if (resultNames != null && !resultNames.isEmpty()) {
				System.out.println("Encontrados " + resultNames.size()
						+ " resultados: ");
				for (int i = 0; i < resultNames.size(); i++) {
					System.out.println(" Resultado " + (i + 1) + ": "
							+ resultNames.get(i) + " " + resultSurnames.get(i));
				}
			} else {
				System.out.println("No se han encontrado resultados.");
			}
		} else {
			System.out.println("Aun no hay contactos cargados");
		}
	}

	/**
	 * Performs the given XPath query against the XML in memory.
	 * 
	 * @param query
	 *            XPath query.
	 * @return
	 */
	private List<String> evaluateXPathQuery(String query) {
		List<String> results = null;
		XPathFactory factory = null;
		XPath xpath = null;
		if (doc != null) {
			results = new ArrayList<String>();
			factory = XPathFactory.newInstance();
			xpath = factory.newXPath();
			try {
				XPathExpression expr = xpath.compile(query);
				Object result = expr.evaluate(doc, XPathConstants.NODESET);
				NodeList nodes = (NodeList) result;
				for (int i = 0; i < nodes.getLength(); i++) {
					results.add(nodes.item(i).getNodeValue());
				}
			} catch (XPathExpressionException ex) {
				System.err.println("Error al buscar el elemento: " + ex);
			}
		}
		return results;
	}

}